using System;

namespace BigNumbers
{
    /// <summary>
    /// This class can be used to create and manage bigRat
    /// A bigrat can be written X / Y where X and Y are BigInt
    /// </summary>
    public class BigRat
    {
        #region Declarations
        private static BigRat m_bi0 = new BigRat(0);
        private static BigRat m_bi1 = new BigRat(1);
        private static BigRat m_bim1 = new BigRat(-1);
        private static bool m_bSimplify = true;
        private BigInt m_biNum;
        private BigInt m_biDen;
        private int m_iSign;
        #endregion

        #region Constructors
        private BigRat()
        {
        }

        public BigRat(BigInt biNum, BigInt biDen)
        {
            if (biDen == BigInt.Zero)
                throw new ArithmeticException("Division by zero");

            m_iSign = biNum.Sign * biDen.Sign;
            m_biNum = Calculus.Abs(biNum);
            m_biDen = Calculus.Abs(biDen);
            if (m_bSimplify) Simplify();
        }

        public BigRat(long iNumber)
        {
            m_iSign = Math.Sign(iNumber);
            m_biDen = BigInt.One;
            m_biNum = new BigInt(Math.Abs(iNumber));
        }

        // TO DO : Create a constructor arg1 = string written a/b
        // public BigRat(string s)
        #endregion

        #region Properties
        public static bool SimplifyOrder
        {
            get
            {
                return m_bSimplify;
            }
            set
            {
                m_bSimplify = value;
            }
        }

        public static BigRat Zero
        {
            get
            {
                return m_bi0;
            }
        }

        public static BigRat One
        {
            get
            {
                return m_bi1;
            }
        }

        public static BigRat MinusOne
        {
            get
            {
                return m_bim1;
            }
        }

        public int Sign
        {
            get
            {
                return m_iSign;
            }
        }
        #endregion

        #region Public Methods
        override public string ToString()
        {
            if (m_iSign == 0)
                return "0";

            string sSign = (m_iSign == 1) ? "" : "-";
            return sSign + m_biNum.ToString() + "/" + m_biDen.ToString();
        }

        /// <summary>
        /// Simplify B / Q
        /// </summary>
        public void Simplify()
        {
            if (m_biNum == BigInt.Zero || m_biNum == BigInt.One)
                return;

            BigInt biPGCD = Calculus.PGCD(m_biNum, m_biDen);
            if (biPGCD > BigInt.One)
            {
                m_biNum = m_biNum / biPGCD;
                m_biDen = m_biDen / biPGCD;
            }
        }

        /// <summary>
        /// HashCode
        /// </summary>
        /// <returns>int</returns>		
        public override int GetHashCode()
        {
            return this.ToString().GetHashCode();
        }

        /// <summary>
        /// Equals
        /// </summary>
        /// <param name="o"></param>
        /// <returns> bool </returns>
        public override bool Equals(object o)
        {
            BigRat br = (BigRat)o;
            return (this == br);
        }

        public BigRat Clone()
        {
            BigRat br = new BigRat();
            br.m_iSign = m_iSign;
            br.m_biDen = m_biDen;
            br.m_biNum = m_biNum;
            if (m_bSimplify) br.Simplify();

            return br;
        }
        #endregion

        #region Public static methods
        public static BigRat Abs(BigRat br)
        {
            BigRat brRes = br.Clone();
            brRes.m_iSign = Math.Abs(br.m_iSign);
            if (m_bSimplify) brRes.Simplify();
            return brRes;
        }

        public static BigRat Min(BigRat br1, BigRat br2)
        {
            if (br1 < br2)
                return br1;
            else
                return br2;
        }

        public static BigRat Max(BigRat br1, BigRat br2)
        {
            if (br1 > br2)
                return br1;
            else
                return br2;
        }

        public static BigRat Pow(BigRat br1, int i)
        {
            if (i < 0)
                throw new ArithmeticException("Can not calculate negative powers");

            if (i == 0)
                return One;

            BigRat brRes = new BigRat();
            if ((i % 2) != 0)
                brRes.m_iSign = br1.m_iSign;
            else
                brRes.m_iSign = 1;

            brRes.m_biNum = Calculus.Pow(br1.m_biNum, i);
            brRes.m_biDen = Calculus.Pow(br1.m_biDen, i);

            return brRes;
        }
        #endregion

        #region Cast
        public static implicit operator BigRat(long i)
        {
            return new BigRat(i);
        }

        public static implicit operator BigRat(BigInt bi)
        {
            BigRat br = new BigRat();
            br.m_biNum = Calculus.Abs(bi);
            br.m_biDen = BigInt.One;
            br.m_iSign = bi.Sign;

            return br;
        }

        // TODO
        // public static explicit operator BigRat (double d)

        // TODO cast : BigRat -> Double, long
        #endregion

        #region Overload unary operators
        public static BigRat operator +(BigRat br)
        {
            BigRat br2 = br.Clone();
            return br2;
        }

        public static BigRat operator -(BigRat br)
        {
            BigRat br2 = br.Clone();
            br2.m_iSign = -br.m_iSign;
            return br2;
        }

        public static BigRat operator ++(BigRat br)
        {
            BigRat brRes = br + One;
            return brRes;
        }

        public static BigRat operator --(BigRat br)
        {
            BigRat brRes = br - One;
            return brRes;
        }
        #endregion

        #region Overload binary operators
        public static BigRat operator +(BigRat br1, BigRat br2)
        {
            BigRat brRes = new BigRat();
            BigInt a1 = br1.m_biNum;
            BigInt b1 = br1.m_biDen;
            BigInt a2 = br2.m_biNum;
            BigInt b2 = br2.m_biDen;
            brRes.m_biNum = br1.m_iSign * a1 * b2 + br2.m_iSign * a2 * b1;
            brRes.m_biDen = Calculus.Abs(b1 * b2);
            brRes.m_iSign = brRes.m_biNum.Sign;
            brRes.m_biNum = Calculus.Abs(brRes.m_biNum);
            if (m_bSimplify) brRes.Simplify();

            return brRes;
        }

        public static BigRat operator -(BigRat br1, BigRat br2)
        {
            return br1 + (-br2);
        }

        public static BigRat operator *(BigRat br1, BigRat br2)
        {
            BigRat brRes = new BigRat();
            brRes.m_iSign = br1.m_iSign * br2.m_iSign;
            brRes.m_biNum = br1.m_biNum * br2.m_biNum;
            brRes.m_biDen = br1.m_biDen * br2.m_biDen;
            if (m_bSimplify) brRes.Simplify();

            return brRes;
        }

        public static BigRat operator /(BigRat br1, BigRat br2)
        {
            if (br2.m_iSign == 0)
                throw new ArithmeticException("Division by zero");

            BigRat brRes = new BigRat();
            brRes.m_iSign = br1.m_iSign * br2.m_iSign;
            brRes.m_biNum = br1.m_biNum * br2.m_biDen;
            brRes.m_biDen = br1.m_biDen * br2.m_biNum;
            if (m_bSimplify) brRes.Simplify();

            return brRes;
        }

        public static bool operator ==(BigRat br1, BigRat br2)
        {

            if (br2.m_iSign != br1.m_iSign)
                return false;

            if (br1.m_biDen == br2.m_biDen && br1.m_biNum == br2.m_biNum)
                return true;

            if (br1.m_biNum * br2.m_biDen == br2.m_biNum * br1.m_biDen)
                return true;
            else
                return false;
        }

        public static bool operator !=(BigRat br1, BigRat br2)
        {
            return !(br1 == br2);
        }

        public static bool operator >(BigRat br1, BigRat br2)
        {
            if (br1.m_iSign > br2.m_iSign)
                return true;

            if (br1.m_iSign < br2.m_iSign)
                return false;

            if (br1.m_iSign == 1)
                return (br1.m_biNum * br2.m_biDen > br2.m_biNum * br1.m_biDen);
            else
                return (br1.m_biNum * br2.m_biDen < br2.m_biNum * br1.m_biDen);
        }

        public static bool operator <(BigRat br1, BigRat br2)
        {
            if (br1.m_iSign < br2.m_iSign)
                return true;

            if (br1.m_iSign > br2.m_iSign)
                return false;

            if (br1.m_iSign == 1)
                return (br1.m_biNum * br2.m_biDen < br2.m_biNum * br1.m_biDen);
            else
                return (br1.m_biNum * br2.m_biDen > br2.m_biNum * br1.m_biDen);
        }

        public static bool operator >=(BigRat br1, BigRat br2)
        {
            return !(br1 < br2);
        }

        public static bool operator <=(BigRat br1, BigRat br2)
        {
            return !(br1 > br2);
        }

        #endregion

        #region Private methods
        #endregion

    }

}
